ES6 的迭代器和生成器
大量参考 ES6系列---迭代器(Iterator)与生成器(Generator) 参考资料 ES6 迭代器
循环语句的问题
var colors = ["red", "green", "blue"];
for(var i=0; i<colors.length; i++){
console.log(colors[i]);
}
在 ES6 之前,这种标准的 for 循环,通过变量来跟踪数组的索引(变量 i)。如果多个循环嵌套就需要追踪多个变量,代码复杂度会大大增加,也容易产生错用循环变量的 bug。
什么是迭代器
用 ES5 语法模拟创建一个迭代器
function createIterator(items) {
var i = 0;
return { // 返回一个迭代器对象
next: function() { // 迭代器对象一定有个next()方法
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return { // next()方法返回结果对象
value: value,
done: done
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next()); // "{ value: undefiend, done: true}"
借助这个迭代器对象,来改造刚开始那个标准的 for 循环
var colors = ["red", "green", "blue"];
var iterator = createIterator(colors);
while(!iterator.next().done){
console.log(iterator.next().value);
}
什么是生成器
生成器是一种返回迭代器的函数,通过 function 关键字后的星号(*)来表示,函数中会用到新的关键字 yield。
function *createIterator(items) {
for(let i=0; i<items.length; i++) {
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
// 既然生成器返回的是迭代器,自然就可以调用迭代器的next()方法
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next()); // "{ value: undefiend, done: true}"
用 ES6 的生成器,大大简化了迭代器的创建过程。给生成器函数 createIterator() 传入一个 items 数组,函数内部,for 循环不断从数组中生成新的元素放入迭代器中,每遇到一个 yield 语句循环都会停止;每次调用迭代器的 next() 方法,循环便继续运行并停止在下一条 yield 语句处。
下面来看下生成器是怎么创建的
ES6 Generator 生成器
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
这个就和 Unity 里使用的协程用法是一致的
Generator 有两个区分于普通函数的部分:
- 在
function后面,函数名之前有个* - 函数内部有
yield表达式。
其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。
下面是它的创建方式
// 生成器是个函数
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
// 生成器内部也可以通过 for 循环不断从数组中生成新的元素放入迭代器中
function *createIterator(items) {
for(let i=0; i<items.length; i++) {
yield items[i];
}
}
// 可以用函数表达式方式书写:
let createIterator = function *(item) { ... }
// 也可以添加到对象中
let o = {
createIterator: function *(items) { ... }
};
let iterator = o.createIterator([1, 2, 3]);
// ES6风格的对象方法简写方式:
let o = {
*createIterator(items) { ... }
};
let iterator = o.createIterator([1, 2, 3]);
可迭代(Iterable)对象
在 ES6 中,所有的集合对象(数组、Set集合及Map集合)和字符串都是可迭代对象,可迭代对象都绑定了默认的迭代器。
var colors = ["red", "green", "blue"];
for(let color of colors){
console.log(color);
}
for-of 循环,可作用在可迭代对象上,正是利用了可迭代对象上的默认迭代器。大致过程是:for-of 循环每执行一次都会调用可迭代对象的 next() 方法,并将迭代器返回的结果对象的 value 属性存储在变量中,循环将继续执行这一过程 直到返回对象的 done 属性的值为 true。
如果只需要迭代数组或集合中的值,可以用 for-of 循环代替 for循环
生成器执行机制
调用 Generator 函数和调用普通函数一样,在函数名后面加上 () 即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象 Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
注意:除了使用 next,还可以使用 for... of 循环遍历 Generator 函数生产的 Iterator 对象。
function* func() {
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
let f = func();
/**
* 第一次调用 next 方法时,从 Generator 函数的头部开始执行,
* 先是打印了 one ,执行到 yield 就停下来,并将 yield 后边表
* 达式的值 '1',作为返回对象的 value 属性值,此时函数还没有
* 执行完, 返回对象的 done 属性值是 false。
*/
console.log(f.next());
// one
// {value: "1", done: false}
/* 第二次调用 next 方法时,同上步 */
console.log(f.next());
// two
// {value: "2", done: false}
/**
* 第三次调用 next 方法时,先是打印了 three ,然后执行了函数
* 的返回操作,并将 return 后面的表达式的值,作为返回对象的
* value 属性值,此时函数已经结束,多以 done 属性值为true 。
*/
console.log(f.next());
// three
// {value: "3", done: true}
/**
* 第四次调用 next 方法时, 此时函数已经执行完了,所以返回
* value 属性值是 undefined ,done 属性值是 true 。如果执
* 行第三步时,没有 return 语句的话,就直接返回 {value: undefined, done: true}。
*/
console.log(f.next());
// {value: undefined, done: true}
访问默认迭代器
可迭代对象,都有一个 Symbol.iterator 方法,for-of 循环时,通过调用 colors 数组的 Symbol.iterator 方法来获取默认迭代器的(这一过程是在 JavaScript 引擎背后完成的)
let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefined, done: true}"
在这段代码中,通过 Symbol.iterator 获取了数组 values 的默认迭代器,并用它遍历数组中的元素。在 JavaScript 引擎中执行 for-of 循环语句也是类似的处理过程。
用 Symbol.iterator 属性来检测对象是否为可迭代对象:
function isIterator(object) {
return typeof object[Symbol.iterator] === "function";
}
console.log(isIterable([1, 2, 3])); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new Map())); // true
console.log(isIterable("Hello")); // true
创建可迭代对象
在创建对象时,给 Symbol.iterator 属性添加一个生成器,则可以将其变成可迭代对象:
let collection = {
items: [],
*[Symbol.iterator]() { // 将生成器赋值给对象的 Symbol.iterator 属性来创建默认的迭代器
for(let item of this.items) {
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
for(let x of collection) {
console.log(x);
}
不同集合的默认迭代器
ES6中的集合对象,数组、Set集合和 Map集合,都内建了三种迭代器:
entries()返回一个迭代器,其值为多个键值对。如果是数组,第一个元素是索引位置;如果是 Set 集合,第一个元素与第二个元素一样,都是值。values()返回一个迭代器,其值为集合的值。keys()返回一个迭代器,其值为集合中的所有键名。如果是数组,返回的是索引;如果是Set集合,返回的是值(Set的值被同时用作键和值)。
每个集合类型都有一个默认的迭代器,在 for-of 循环中,如果没有显式指定则使用默认的迭代器。按常规使用习惯,数组和 Set 集合的默认迭代器是 values()